﻿// -----------------------------------------------------------------------
// <copyright file="OracleInstaller.cs" company="JSC Centido">
//  Copyright (c) JSC Centido. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

namespace Centido.Core
{
    using System;
    using System.Linq;
    using System.Text.RegularExpressions;
    using Properties;

    /// <summary>
    ///   An Oracle metadata installer.
    /// </summary>
    /// <remarks>System.Data.OracleClient ADO.NET provider cannot be used
    /// to connect with elevated privileges (as DBA).</remarks>
    public sealed class OracleInstaller : Installer
    {
        /// <summary>
        ///   Gets the default tablespace name.
        /// </summary>
        public const string DefaultTablespaceName = "USERS";

        /// <summary>
        ///   Initializes a new instance of the <see cref="Centido.Core.OracleInstaller"/> class.
        /// </summary>
        public OracleInstaller()
        {
            
        }

        /// <summary>
        ///   Initializes a new instance of the <see cref="Centido.Core.OracleInstaller"/> class.
        /// </summary>
        /// <param name="dbConnectionString">The connection string.</param>
        /// <param name="dbProviderName">The database provider invariant name.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="dbConnectionString"/> is null.</exception>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="dbProviderName"/> is null.</exception>
        /// <exception cref="ArgumentException">Thrown when the value of <paramref name="dbConnectionString"/> is an empty string.</exception>
        /// <exception cref="ArgumentException">Thrown when the value of <paramref name="dbProviderName"/> is an empty string.</exception>        
        public OracleInstaller(string dbConnectionString, string dbProviderName)
            : base(dbConnectionString, dbProviderName)
        {
            
        }


        /// <summary>
        ///   Gets the database product name.
        /// </summary>
        public override string ProductName
        {
            get { return "ORACLE"; }
        }

        /// <summary>
        ///   Gets the version of the metadata schema.
        /// </summary>
        public override int SchemaVersion
        {
            get { return 3; }
        }

        /// <summary>
        ///   Gets the version of the metadata logic.
        /// </summary>
        public override int LogicVersion
        {
            get { return 2; }
        }

        /// <summary>
        ///   Installs the metadata schema.
        /// </summary>
        /// <returns>Returns the current installer.</returns>
        public override Installer InstallSchema()
        {
            ExecuteScript(Resources.Installation_schema_oracle);

            return this;
        }

        /// <summary>
        ///   Installs the metadata logic.
        /// </summary>
        /// <returns>Returns the current installer.</returns>
        public override Installer InstallLogic()
        {
            ExecuteScript(Resources.Installation_logic_oracle);

            RecompileStoredProcedures();

            return this;
        }


        /// <summary>
        ///   Recompiles all stored procedures.
        /// </summary>
        private void RecompileStoredProcedures()
        {
            var builder = ConnectionStringBuilders.GetBuilder(DbProviderName);

            if (builder == null)
                throw new ArgumentException("Cannot find a connection builder for the specified db provider.");

            builder.Parse(DbConnectionString);

            ExecuteScript(
                string.Format("{{call DBMS_UTILITY.compile_schema('{0}')}}",
                    builder.UserName.ToUpper()));
        }


        /// <summary>
        ///   Creates an application user with the specified name and password.
        /// </summary>
        /// <param name = "userName">The application user name.</param>
        /// <param name="userPassword">The application user password</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="userName"/> is null.</exception>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="userPassword"/> is null.</exception>
        /// <exception cref="ArgumentException">Thrown when the value of <paramref name="userPassword"/> is an empty string.</exception>
        /// <returns>Returns the current installer.</returns>
        public Installer CreateAppUser(string userName, string userPassword)
        {
            return CreateAppUser(userName, userPassword, null);
        }


        /// <summary>
        ///   Creates an application user with the specified name and password.
        /// </summary>
        /// <remarks>The parameter that specifies the application database name can be
        /// omitted because the metadata is always installed in the application user schema.</remarks>
        /// <param name = "userName">The application user name.</param>
        /// <param name="userPassword">The application user password</param>
        /// <param name="dbName">The application user database name.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="userName"/> is null.</exception>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="userPassword"/> is null.</exception>
        /// <exception cref="ArgumentException">Thrown when the value of <paramref name="dbName"/> is an empty string.</exception>
        /// <exception cref="ArgumentException">Thrown when the value of <paramref name="userPassword"/> is an empty string.</exception>
        /// <exception cref="ArgumentException">Thrown when the value of <paramref name="userName"/> is not a valid identifier.</exception>
        /// <returns>Returns the current installer.</returns>
        public override Installer CreateAppUser(string userName, string userPassword, string dbName)
        {
            if (userName == null)
                throw new ArgumentNullException("userName");
            if (userPassword == null)
                throw new ArgumentNullException("userPassword");

            // The db name can be null

            if (userName.Trim().Length == 0)
                throw new ArgumentException("The application user name cannot be empty.");
            if (userPassword.Trim().Length == 0)
                throw new ArgumentException("The application user password cannot be empty.");

            userName = userName.Trim();

            DbRule.TestIdentifier(userName);

            ExecuteScript(GetAppUserInstallationScript(userName, userPassword, dbName));

            return this;
        }


        /// <summary>
        ///   Retrieves the application user installation script.
        /// </summary>
        /// <param name="userName">The application user name.</param>
        /// <param name="userPassword">The application user password.</param>
        /// <param name="dbName">The application user database name.</param>
        /// <returns>Returns the application user installation script.</returns>
        public override string GetAppUserInstallationScript(string userName, string userPassword, string dbName)
        {
            if (userName == null)
                throw new ArgumentNullException("userName");
            if (userPassword == null)
                throw new ArgumentNullException("userPassword");

            // The db name can be null

            userName = userName.Trim();

            var script = Resources.Installation_meta_owner_oracle;

            if (!string.IsNullOrEmpty(userName))
                script = Regex.Replace(script, @"@@user_name@@", userName);
            if (!string.IsNullOrEmpty(userPassword))
                script = Regex.Replace(script, @"@@user_password@@", userPassword);

            script = Regex.Replace(script, @"@@default_tablespace@@", DefaultTablespaceName);

            return script;
        }


        /// <summary>
        ///    Uninstalls the metadata.
        /// </summary>
        public override Installer Uninstall()
        {
            ExecuteScript(Resources.Uninstallation_oracle);

            return this;
        }

        /// <summary>
        ///   Executes an sql script.
        /// </summary>
        /// <param name = "sqlScript">The text of the sql script.</param>
        private void ExecuteScript(string sqlScript)
        {
            var dbAccess = new DbAccess(DbConnectionString);

            var regex = new Regex("--go", RegexOptions.IgnoreCase | RegexOptions.Multiline);

            var lines = regex.Split(sqlScript);

            foreach (var commandStr in
                lines.Select(line => line.Replace("\r\n", "\n").Trim()).Where(commandStr => commandStr.Length != 0))
            {
                var commandToExecuteStr = commandStr;

                if (commandToExecuteStr.EndsWith(";"))
                {
                    // If it is not an anonimous block
                    if (!commandToExecuteStr.ToLower().EndsWith("end;"))
                    {
                        // There is no need for ; in the end
                        commandToExecuteStr = commandToExecuteStr.Substring(0, commandToExecuteStr.Length - 1);
                    }
                }

                dbAccess.ExecuteNonQuery(cmd => { cmd.CommandText = commandToExecuteStr; });
            }
        }
    }
}